package com.lgj.gateway.route;

import com.lgj.gateway.jdbc.RouteRepository;
import com.lgj.gateway.jdbc.model.GatewayRoute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author guangjie.liao
 * @Date: 2022/10/24 19:09
 * @Description: 动态路由实现
 */
@Slf4j
public class GateWayDynamicRoute implements ApplicationEventPublisherAware {
    /**
     * 路由缓存方式
     * 可以是InMemory 和 Redis，在路由集群部署下建议使用Redis，
     * 使用InMemory 需要通知到各个节点才能全部刷新
     */
    private RouteDefinitionRepository repository;
    /**
     * 路由刷新事件
     */
    private ApplicationEventPublisher publisher;
    /**
     * 路由持久化
     * 通过读取mysql持久化的路由信息我们可以在后台配置化路由信息
     */
    private RouteRepository routeRepository;


    public GateWayDynamicRoute(RouteDefinitionRepository repository,
                               ApplicationEventPublisher publisher,
                               RouteRepository routeRepository){
        this.repository = repository;
        this.publisher = publisher;
        this.routeRepository = routeRepository;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 刷新路由
     * @return
     */
    public Mono<Void> refresh() {
        this.loadRoute();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.empty();
    }

    /**
     * 加载所有路由
     */
    public void loadRoute(){
        List<RouteDefinition> routeDefinitions = getRouteDefinition();
        //每次加载前把缓存的全部清空
        routeDefinitions.stream().forEach(routeDefinition -> {
            repository.delete(Mono.just(routeDefinition.getId())).subscribe();
        });
        routeDefinitions.stream().forEach(routeDefinition -> {
            repository.save(Mono.just(routeDefinition)).subscribe();
        });
        log.info("动态加载路由：{}",routeDefinitions.size());
    }

    /**
     * 构建 RouteDefinition
     * RouteDefinition 为路由对象，包含断言和filter
     * @return
     */
    private List<RouteDefinition> getRouteDefinition(){
        List<RouteDefinition> routeDefinitions = new ArrayList<>();

        List<GatewayRoute> routeList = routeRepository.selectAllRoute();
        if (CollectionUtils.isEmpty(routeList)){
            return routeDefinitions;
        }
        routeList.stream().forEach(route->{
            RouteDefinition routeDef =new RouteDefinition();
            routeDef.setPredicates(getPredicateDefs(route));
            routeDef.setFilters(getFilters(route));

            routeDef.setId(route.getRouteName());
            routeDef.setUri(getServerUri(route));
            routeDefinitions.add(routeDef);
        });
        return routeDefinitions;
    }

    /**
     * 构建断言
     * @param route
     * @return
     */
    private List<PredicateDefinition> getPredicateDefs(GatewayRoute route){
        List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
        PredicateDefinition predicatePath = new PredicateDefinition();
        Map<String, String> predicatePathParams = new HashMap<>(8);
        predicatePath.setName("Path");
        predicatePathParams.put("name", route.getRouteName());
        predicatePathParams.put("pattern", route.getPath());
        predicatePathParams.put("pathPattern", route.getPath());
        predicatePath.setArgs(predicatePathParams);
        predicateDefinitions.add(predicatePath);
        return predicateDefinitions;
    }

    /**
     * 构建 filter
     * @param route
     * @return
     */
    private List<FilterDefinition> getFilters(GatewayRoute route){
        List<FilterDefinition> filterDefinitions = new ArrayList<>();

        FilterDefinition retryDefinition = new FilterDefinition();
        Map<String, String> retryParams = new HashMap<>(8);
        retryDefinition.setName("Retry");
        retryParams.put("exceptions", "java.lang.Exception");
        retryDefinition.setArgs(retryParams);

        FilterDefinition stripPrefixDefinition = new FilterDefinition();
        Map<String, String> stripPrefixParams = new HashMap<>(8);
        stripPrefixDefinition.setName("StripPrefix");
        stripPrefixParams.put(NameUtils.generateName(0), route.getStripPrefix()+"");
        stripPrefixDefinition.setArgs(stripPrefixParams);

        filterDefinitions.add(retryDefinition);
        filterDefinitions.add(stripPrefixDefinition);
        return filterDefinitions;
    }

    /**
     * 构建路由地址
     * @param route
     * @return
     */
    private URI getServerUri(GatewayRoute route){
        String urlStr = route.getUrl() + route.getServiceId() ;
        URI uri = UriComponentsBuilder.fromUriString(urlStr).build().toUri();
        return uri;
    }
}
